/**
 * 
 * 
 * 
 * Usage:
 * 
 * // create the connection
 * var cnx = haxe.remoting.HttpAsyncConnection.urlConnect("http://myurl");
 *       
 * // create your proxy
 * var proxy = new ServerProxy(cnx.serverApi);
 *   
 * // define callbacks
 * var cb1 = function (s) trace("Queue1: " + Std.string(s));
 * var cb2 = function (s) trace("Queue2: " + Std.string(s));
 * var cb3 = function (s) trace("Queue3: " + Std.string(s));
 * var cb4 = function (s) trace("Queue4: " + Std.string(s));
 * var cb5 = function (s) trace("Queue5: " + Std.string(s));
 *   
 * // Remoting Queue
 *   
 * var queue = new RemotingQueue();
 *   
 * // add remoting calls to the queue to avoid unspecific timings for your callbacks
 *  
 * queue.addVoid(proxy.getString, cb1);
 * queue.addVoid(proxy.getObj, cb2);
 * queue.add1(proxy.hello, "peter", cb3);
 * queue.addVoid(proxy.getDBResult, cb4);
 * queue.add3(proxy.make, "jack", 1, 18, cb5);
 * 
 */

package bloom.remoting;

class RemotingQueue 
{
    var list:List<{fun:Dynamic, handler:Dynamic, args:Array<Dynamic>}>;
    var calling:Bool;
    
    public function new() 
    {
        list = new List();
        calling = false;
    }
    
    //------------------------ PUBLIC ------------------------------------------
    
    //-------------- Remoting Functions returning Void, No Handler -------------
    
    public function addVoid0 (fun:Void->Void) 
    {
        add(fun, null, []);
    }
    
    public function addVoid1 <S>(fun:S->Void, arg:S) 
    {
        add(fun, null, [arg]);
    }
    
    public function addVoid2 <S,T>(fun:S->T->Void, arg1:S, arg2:T) 
    {
        add(fun, null, [arg1, arg2]);
    }
    
    public function addVoid3 <S,T,U>(fun:S->T->U->Void, arg1:S, arg2:T, arg3:U) 
    {
        add(fun, null, [arg1, arg2, arg3]);
    }
    
    public function addVoid4 <S,T,U,V>(fun:S->T->U->V->Void, arg1:S, arg2:T, arg3:U, arg4:V) 
    {
        add(fun, null, [arg1, arg2, arg3, arg4]);
    }
    
    public function addVoid5 <S,T,U,V,W>(fun:S->T->U->V->W->Void, arg1:S, arg2:T, arg3:U, arg4:V, arg5:W) 
    {
        add(fun, null, [arg1, arg2, arg3, arg4, arg5]);
    }
    
    //-------------- Remoting Functions return not Void with Handler -------------
    
    public function add0 <T>(fun:T->Void, handler:T) 
    {
        add(fun, handler, []);
    }
    
    /**
     * Adds a Remoting Function with 1 Parameter to the Remoting Queue.
     * 
     * @param fun       remoting function to call
     * @param arg       only argument for the remoting function
     * @param handler   callback handler
     */
    public function add1 <T,D>(fun:D->T->Void, arg:D, handler:T) 
    {
        add(fun, handler, [arg]);
    }
    
    public function add2 <T,C,D>(fun:C->D->T->Void, arg1:C, arg2:D, handler:T) 
    {
        add(fun, handler, [arg1,arg2]);
    }
    
    public function add3 <T,B,C,D>(fun:B->C->D->T->Void, arg1:B, arg2:C, arg3:D, handler:T) 
    {
        add(fun, handler, [arg1,arg2, arg3]);
    }
    
    public function add4 <T,B,C,D,E>(fun:B->C->D->E->T->Void, arg1:B, arg2:C, arg3:D, arg4:E, handler:T) 
    {
        add(fun, handler, [arg1,arg2, arg3, arg4]);
    }
    
    public function add5 <T,B,C,D,E,F>(fun:B->C->D->E->F->T->Void, arg1:B, arg2:C, arg3:D, arg4:E, arg5:F, handler:T) 
    {
        add(fun, handler, [arg1,arg2, arg3, arg4, arg5]);
    }
    
    
    //------------------------ PRIVATE ------------------------------------------
    
    function add (fun:Dynamic, handler:Dynamic, args:Array<Dynamic>)
    {
        list.add( { fun:fun, handler:handler, args:args } );
        
        resume();
    }
    
    function resume () 
    {
        if (!calling && !list.isEmpty()) call();
    }
    
    function call()
    {
        
        
        var cur = list.first();
        list.remove(cur);
        
        if (cur.handler != null) 
        {
            calling = true;
            var self = this;
            
            var f = function (arr) self.handle(arr, cur.handler);
            
            cur.args.push(Reflect.makeVarArgs(f));
            
            Reflect.callMethod(null, cur.fun, cur.args);
        } else {
            // function has no handler so just call it and move on
            Reflect.callMethod(null, cur.fun, cur.args);
            resume();
        }
    }
    
    function handle (args:Array<Dynamic>, handler:Dynamic)
    {
        Reflect.callMethod(null, handler, args);
        calling = false;
        resume();
    } 
}